package com.tatesuke.automine.board.mine2k;

import java.awt.AWTException;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.awt.image.BufferedImage;

import com.tatesuke.automine.board.Board;
import com.tatesuke.automine.board.BoardState;
import com.tatesuke.automine.board.CellState;

public class Mine2KBoard implements Board {

	static final int CELL_TYPE_UNKNOWN = 9;
	static final int CELL_TYPE_FLAG = 10;
	static final int CELL_TYPE_BOMB = 11;
	static final int CELL_TYPE_BURN = 12;
	static final int CELL_TYPE_MISS = 13;
	static final int CELL_TYPE_UNDEFINED = 14;

	static final int CELL_SIZE = 16;

	private final int rowSize;
	private final int columnSize;
	private final int bombCount;
	private final Point basePoint;
	private final long clickSleepTime;
	private final long captureSleepTime;

	private final Robot robot;
	private final Rectangle captureArea;
	private final CellTypeInferencer inferencer;

	private int[][] cellType;
	private boolean[][] isFlag;
	private boolean isBurn;
	private boolean isClear;

	/**
	 * @param rowSize
	 *            行サイズ
	 * @param columnSize
	 *            列サイズ
	 * @param bombCount
	 *            爆弾の数
	 * @param basePoint
	 *            0行目、0列目のセルの左上のスクリーン座標
	 * @param clickSleep
	 *            クリック後のスリープ時間
	 * @param captureSleep
	 *            画面キャプチャ後のスリープ時間
	 * @throws AWTException
	 */
	public Mine2KBoard(int rowSize, int columnSize, int bombCount, Point basePoint,
			long clickSleep, long captureSleep) throws AWTException {
		this.rowSize = rowSize;
		this.columnSize = columnSize;
		this.bombCount = bombCount;
		this.basePoint = basePoint;
		this.clickSleepTime = clickSleep;
		this.captureSleepTime = captureSleep;

		robot = new Robot();
		robot.setAutoWaitForIdle(true);
		captureArea = new Rectangle(basePoint.x, basePoint.y, CELL_SIZE
				* columnSize, CELL_SIZE * rowSize);
		cellType = new int[rowSize][columnSize];
		isFlag = new boolean[rowSize][columnSize];
		inferencer = new CellTypeInferencer();

		removeFlag();
		recognize();
	}

	private void removeFlag() {
		captureImage();
		for (int row = 0; row < rowSize; row++) {
			for (int column = 0; column < columnSize; column++) {
				int type = inferencer.getCellType(row, column);
				if (type == CELL_TYPE_FLAG) {
					clickCell(InputEvent.BUTTON3_MASK, row, column);
				}
			}
		}
	}

	private void recognize() {
		captureImage();
		int openCount = 0;
		for (int row = 0; row < rowSize; row++) {
			for (int column = 0; column < columnSize; column++) {
				int type = inferencer.getCellType(row, column);
				cellType[row][column] = type;
				switch (type) {
				case CELL_TYPE_BURN:
					isBurn = true;
					break;
				case CELL_TYPE_UNDEFINED:
					isClear = true;
					break;
				case CELL_TYPE_UNKNOWN:
					openCount++;
					break;
				}
			}
		}

		if (openCount == 0) {
			isClear = true;
		}
	}

	private void captureImage() {
		try {
			BufferedImage image = robot.createScreenCapture(captureArea);
			Thread.sleep(captureSleepTime);
			inferencer.setImage(image);
		} catch (InterruptedException e) {
			/* どうしよもないので握りつぶし */
			e.printStackTrace();
		}
	}

	@Override
	public int getRowSize() {
		return rowSize;
	}

	@Override
	public int getColumnSize() {
		return columnSize;
	}

	@Override
	public int getBombCount() {
		return bombCount;
	}

	@Override
	public void reset() {
		int x = (CELL_SIZE * columnSize) / 2;
		int y = basePoint.y -16;

		click(InputEvent.BUTTON1_MASK, x, y);

		isBurn = false;
		recognize();
	}

	@Override
	public CellState getCellState(int row, int column) {
		if (row < 0 || getRowSize() <= row || column < 0
				|| getColumnSize() <= column) {
			return CellState.SENTRY;
		}



		switch (cellType[row][column]) {
		case 0:
		case 1:
		case 2:
		case 3:
		case 4:
		case 5:
		case 6:
		case 7:
		case 8:
			return CellState.OPEN;
		case CELL_TYPE_UNKNOWN:
			return CellState.CLOSE;
		case CELL_TYPE_FLAG:
			return CellState.FLAG;
		case CELL_TYPE_BOMB:
		case CELL_TYPE_BURN:
		case CELL_TYPE_MISS:
			return CellState.OTHER;
		case CELL_TYPE_UNDEFINED:
		default:
			throw new IllegalStateException("未知のセル");
		}
	}

	@Override
	public void open(int row, int column) {
		checkRange(row, column);

		if (isBurn() || isClear()) {
			return;
		}

		if (cellType[row][column] == CELL_TYPE_UNKNOWN) {
			clickCell(InputEvent.BUTTON1_MASK, row, column);
			recognize();
		}
	}

	@Override
	public void toggleFlag(int row, int column) {
		checkRange(row, column);

		if (isBurn() || isClear()) {
			return;
		}

		if (isFlag[row][column]) {
			isFlag[row][column] = false;
			cellType[row][column] = CELL_TYPE_UNKNOWN;
			clickCell(InputEvent.BUTTON3_MASK, row, column);
		} else if (cellType[row][column] == CELL_TYPE_UNKNOWN) {
			isFlag[row][column] = true;
			cellType[row][column] = CELL_TYPE_FLAG;
			clickCell(InputEvent.BUTTON3_MASK, row, column);
		}

	}

	private void clickCell(int buttonMask, int row, int column) {
		int x = (column * CELL_SIZE) + (CELL_SIZE / 2) + basePoint.x;
		int y = (row * CELL_SIZE) + (CELL_SIZE / 2) + basePoint.y;
		click(buttonMask, x, y);
	}

	private void click(int buttonMask, int x, int y) {
		try {
			robot.mouseMove(x, y);
			Thread.sleep(clickSleepTime);
			robot.mousePress(buttonMask);
			Thread.sleep(clickSleepTime);
			robot.mouseRelease(buttonMask);
			Thread.sleep(clickSleepTime);
		} catch (InterruptedException e) {
			/* どうしようもないので握り潰す */
			e.printStackTrace();
		}
	}

	public boolean isClear() {
		return isClear;
	}

	public boolean isBurn() {
		return isBurn;
	}

	@Override
	public int getHint(int row, int column) {
		checkRange(row, column);

		int type = cellType[row][column];

		if (type < 0 || 8 < type) {
			throw new IllegalStateException();
		}

		return type;
	}

	private void checkRange(int row, int column) {
		if (row < 0 || getRowSize() <= row || column < 0
				|| getColumnSize() < column) {
			throw new IndexOutOfBoundsException();
		}
	}

	@Override
	public void openAllNeighbors(int row, int column) {
		// TODO 自動生成されたメソッド・スタブ

	}

	@Override
	public BoardState getState() {
		// TODO 自動生成されたメソッド・スタブ
		return null;
	}

}
